package com.saascloud.web.core.handler;

import com.saascloud.core.exception.BizException;
import com.saascloud.core.exception.GlobalErrorCode;
import com.saascloud.core.pojo.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;


/**
 * 全局异常处理器，将 Exception 翻译成 R + 对应的异常编号
 * 可以通过exception.handler.enabled:false禁用异常处理器
 */
@ControllerAdvice
@Slf4j
@ConditionalOnMissingBean(GlobalExceptionHandler.class)
public class GlobalExceptionHandler {


    @Value("${exception.resp.code:200}")
    private int bizExceptionHttpStatus;

    /**
     * 处理 SpringMVC 请求参数缺失
     * <p>
     * 例如说，接口上设置了 @RequestParam("xx") 参数，结果并未传递 xx 参数
     */
    @ExceptionHandler(value = {MissingServletRequestParameterException.class})
    @ResponseBody
    public R<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex, HttpServletResponse response) {
        log.warn("[missingServletRequestParameterExceptionHandler]", ex);
        response.setStatus(bizExceptionHttpStatus);
        return R.error(GlobalErrorCode.BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()));
    }

    @ExceptionHandler(value = {HttpMessageNotReadableException.class})
    @ResponseBody
    public R<?> httpMessageNotReadableException(HttpMessageNotReadableException ex, HttpServletResponse response) {
        log.warn("[HttpMessageNotReadableException]", ex);
        response.setStatus(bizExceptionHttpStatus);
        return R.error(GlobalErrorCode.BAD_REQUEST.getCode(), "请求参数获取失败");
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     * <p>
     * 例如说，接口上设置了 @RequestParam("xx") 参数为 Integer，结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseBody
    public R<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex, HttpServletResponse response) {
        log.warn("[methodArgumentTypeMismatchExceptionHandler]", ex);
        response.setStatus(bizExceptionHttpStatus);
        return R.error(GlobalErrorCode.BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
    }

    /**
     * 处理 SpringMVC 参数校验不正确
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public R<?> methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex, HttpServletResponse response) {
        log.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
        FieldError fieldError = ex.getBindingResult().getFieldError();
        response.setStatus(bizExceptionHttpStatus);
        return R.error(GlobalErrorCode.BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
    }

    /**
     * 处理 SpringMVC 参数绑定不正确，本质上也是通过 Validator 校验
     */
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public R<?> bindExceptionHandler(BindException ex, HttpServletResponse response) {
        log.warn("[handleBindException]", ex);
        FieldError fieldError = ex.getFieldError();
        response.setStatus(bizExceptionHttpStatus);
        return R.error(GlobalErrorCode.BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
    }

    /**
     * 处理 Validator 校验不通过产生的异常
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    @ResponseBody
    public R<?> constraintViolationExceptionHandler(ConstraintViolationException ex, HttpServletResponse response) {
        log.warn("[constraintViolationExceptionHandler]", ex);
        response.setStatus(bizExceptionHttpStatus);
        ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
        return R.error(GlobalErrorCode.BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", constraintViolation.getMessage()));
    }


    /**
     * 处理 SpringMVC 请求方法不正确
     * <p>
     * 例如说，A 接口的方法为 GET 方式，结果请求方法为 POST 方式，导致不匹配
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseBody
    public R<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex, HttpServletResponse response) {
        log.warn("[httpRequestMethodNotSupportedExceptionHandler]", ex);
        response.setStatus(bizExceptionHttpStatus);
        return R.error(GlobalErrorCode.METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage()));
    }

    /**
     * 处理业务异常 BizException
     * <p>
     */
    @ExceptionHandler(value = BizException.class)
    @ResponseBody
    public R<?> bizExceptionHandler(BizException ex, HttpServletResponse response) {
        log.info("[bizExceptionHandler]:{}", ex.toString(), ex);
        response.setStatus(bizExceptionHttpStatus);
        return R.error(ex.getErrorCode(), ex.getErrorMsg());
    }

    @ExceptionHandler(value = Throwable.class)
    @ResponseBody
    public R<?> defaultThrowableHandler(Throwable ex, HttpServletResponse response) {
        log.info("[defaultThrowableHandler]:{}", ex.toString(), ex);
        Object cause = ex.getCause();
        BizException bizException = cause instanceof BizException ? ((BizException) cause) : null;
        if (bizException != null) {
            response.setStatus(bizExceptionHttpStatus);
            return R.error(bizException.getErrorCode(), bizException.getErrorMsg());
        }
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        return R.error(GlobalErrorCode.INTERNAL_SERVER_ERROR);
    }

}
